﻿namespace EFS.RC.CC.Sessions.OfficeService.Operators
{
	/// <summary>
	/// Operator session
	/// </summary>
	internal abstract class Operator : EFS.RC.CC.Sessions.OfficeService.Participant
	{
		#region Composition

		// Office service conference
		private EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession = null;

		// Agent presence
		private EFS.RC.CC.Presences.Operators.Presence operatorPresence = null;

		// Context channel 
		private EFS.RC.CC.Channels.Contexts.Operators.Operator contextChannel = null;

		// update ticker
		private EFS.RC.CC.Tools.Repeater.Callback contextChannelUpdateRepeater = null;

		// Tone controller
		private EFS.RC.CC.Tools.Tone.Controler toneController = null;

		// Players
		private System.Collections.Generic.List<EFS.RC.CC.Tools.Players.Audio> players = new System.Collections.Generic.List<EFS.RC.CC.Tools.Players.Audio>();

		/// <summary>
		/// Participant end point Uri (must be SIP)
		/// </summary>
		private System.String participantEndPointUri = null;

		/// <summary>
		/// Status
		/// </summary>
		public enum SessionStatus { Idle, Started, Terminated }

		// Status
		private SessionStatus status = SessionStatus.Idle;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="operatorPresence"></param>
		public Operator(EFS.RC.CC.Presences.Operators.Presence operatorPresence, EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession)
		{
			// check
			EFS.Common.Verify.Reference(operatorPresence, "operatorPresence");
			EFS.Common.Verify.Reference(customerSession, "customerSession");

			// set
			this.operatorPresence = operatorPresence;
			this.customerSession = customerSession;

			// sip only can be used as participant end point uri
			participantEndPointUri = operatorPresence.User.SIP.Replace(";user=phone", "");
		}

		#endregion

		#region Usage

		/// <summary>
		/// Get customer session
		/// </summary>
		public EFS.RC.CC.Sessions.OfficeService.Customers.Customer CustomerSession
		{
			get { return customerSession; }
		}

		/// <summary>
		/// Office service conference
		/// </summary>
		public override EFS.RC.CC.Confereces.OfficeService OfficeServiceConference 
		{
			get { return customerSession.OfficeServiceConference; }
 		}

		/// <summary>
		/// Impersonated uri
		/// </summary>
		public override System.String ParticipantEndPointUri
		{
			get 
			{
				return participantEndPointUri; 
			}
		}

		/// <summary>
		/// Remote conversation id
		/// </summary>
		public virtual System.String RemoteConversationId
		{
			get { return null; }
		}

		/// <summary>
		/// Agent presence
		/// </summary>
		public EFS.RC.CC.Presences.Operators.Presence OperatorPresence
		{
			get { return operatorPresence; }
		}

		/// Status
		public SessionStatus Status { get { return status; } }

		/// <summary>
		/// Begin startup
		/// </summary>
		public override EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// if agent, register session
			if (operatorPresence is EFS.RC.CC.Presences.Operators.OfficeService.Agent)
			{
				// register session
				(operatorPresence as EFS.RC.CC.Presences.Operators.OfficeService.Agent).RegisterOperatorSession(this);
			}

			// start
			EFS.Common.Async.AsyncProcess asyncProcess = BeginAsyncProcess2(Startup, OnShutdownComplete);

			// check
			if (null == asyncProcess)
			{
				// shut down will also clean-up the session
				BeginShutdown();
			}

			// done
			return asyncProcess;
		}

		/// <summary>
		/// Begin shutdown
		/// </summary>
		public override EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginShutdown", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		/// <summary>
		/// Begin wrap
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginWrap()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginWrap", this);

			// start
			return BeginAsyncProcess2(Wrap);
		}

		/// <summary>
		/// Start dial out music
		/// </summary>
		public void BeginStartDialPlayer()
		{
			// start async
			BeginAsyncProcess2(StartDialPlayer);
		}

		/// <summary>
		/// Start dial busy music
		/// </summary>
		public void BeginStartBusyPlayer()
		{
			// start async
			BeginAsyncProcess2(StartBusyPlayer);
		}

		/// <summary>
		/// Start dial error music
		/// </summary>
		public void BeginStartDialErrorPlayer()
		{
			// start async
			BeginAsyncProcess2(StartDialErrorPlayer);
		}

		/// <summary>
		/// Start misic for hunting agent
		/// </summary>
		/// <returns></returns>
		public void BeginStopPlayer()
		{
			// start async
			BeginAsyncProcess2(StopPlayers);
		}

		/// <summary>
		/// Send tone
		/// </summary>
		/// <param name="tone"></param>
		public void SendTone(System.Int32 tone, System.Single volume)
		{
			// log
			EFS.EventLog.Log.LogInfo("InitToneController", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null == toneController)
				{
					throw new System.InvalidOperationException("Tone Controler not Initialized");
				}

				// make controller
				toneController.Send(tone, volume);
			}
		}

		/// <summary>
		/// Send message
		/// </summary>
		/// <param name="message"></param>
		public void SendMessage(EFS.RC.CC.Messages.Message message)
		{
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != contextChannel)
				{
					// send info
					contextChannel.BeginDataSend(message);
				}
			}
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		private void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != SessionStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Agent Session can be started");
				}

				// started
				status = SessionStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "Startup", this);

			// CH
			EFS.RC.CC.Tools.Loggers.CHL.Add(customerSession.ConversationGuid, null, null, operatorPresence.User.ObjectGuid, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Start);

			// register terminate delegate
			customerSession.OfficeServiceConference.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceTerminated);	

			// setup session
			BeginAsyncProcess2(SetupSession);
		}

		/// <summary>
		/// Setup agent session
		/// </summary>
		protected abstract void SetupSession();
	
		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnException", ex, this); 

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Called when participant end point added
		/// </summary>
		protected override void ParticipantEndPointAdded()
		{
			// log
			EFS.EventLog.Log.LogInfo("Operator: ParticipantEndPointAdded", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "ParticipantEndPointAdded", this);

			// init tone controller
			BeginAsyncProcess2(InitToneController);

			// tone
			if (OperatorPresence is EFS.RC.CC.Presences.Operators.OfficeService.Agent)
			{
				// make my channel
				BeginAsyncProcess2(InitContextChannel);
			}
		}

		/// <summary>
		/// Called when participant end point removed
		/// </summary>
		protected override void ParticipantEndPointRemoved()
		{
			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Wrap
		/// </summary>
		private void Wrap()
		{
			// log
			EFS.EventLog.Log.LogInfo("Wrap", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "Wrap", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != contextChannel)
				{
					// lock local acess
					// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
					contextChannel.BeginDataSend(new EFS.RC.CC.Messages.Communicator.Lync.Conversation.WrapWarning());
				}
			}
		}

		/// <summary>
		/// Start misic for hunting agent
		/// </summary>
		/// <returns></returns>
		private void StopPlayers()
		{
			// log
			EFS.EventLog.Log.LogInfo("StopPlayers", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "StopPlayers", this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop all playes if any
				foreach (EFS.RC.CC.Tools.Players.Audio player in players)
				{
					// stop
					player.BeginShutdown();
				}
			}
		}

		/// <summary>
		/// Start dial out music
		/// </summary>
		private void StartDialPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("StartDialPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "StartDialPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForCalling, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// srart
				player.BeginStartup();

				// add
				players.Add(player);
			}
		}

		/// <summary>
		/// Start busy out music
		/// </summary>
		private void StartBusyPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("StartBusyPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "StartBusyPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForCustomerBusy, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// srart
				player.BeginStartup();

				// add
				players.Add(player);
			}
		}

		/// <summary>
		/// Start busy error music
		/// </summary>
		private void StartDialErrorPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("StartDialErrorPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "StartDialErrorPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForCustomerDialError, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// srart
				player.BeginStartup();

				// add
				players.Add(player);
			}
		}

		/// <summary>
		/// Init context
		/// </summary>
		private void InitContextChannel()
		{
			// log
			EFS.EventLog.Log.LogInfo("InitContextChannel", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "InitContextChannel", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != contextChannel)
				{
					throw new System.InvalidOperationException("Context Channel already Initialized");
				}

				// check if dial out, no context
				if (!(this is EFS.RC.CC.Sessions.OfficeService.Operators.DialOut) && !(this is EFS.RC.CC.Sessions.OfficeService.Operators.Invited))
				{
					// make new
					contextChannel = new Channels.Contexts.Operators.Operator("agent", CustomerSession.OfficeServiceConference.Conversation1, ParticipantRemoteEndPoint, OnContextEstablished, OnContextMessageReceived, RemoteConversationId);
					contextChannel.BeginStartup();
				}
			}
		}

		/// <summary>
		/// Context message received
		/// </summary>
		/// <param name="message"></param>
		private void OnContextMessageReceived(EFS.RC.CC.Messages.Message message)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnContextMessageReceived", this);

			// check
			EFS.Common.Verify.Reference(message, "message");

			// check status
			if (status != SessionStatus.Started)
			{
				throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
			}

			try
			{
				// application context message regeived
				if (message is EFS.RC.CC.Messages.Communicator.Lync.Context.Aplication)
				{
					// get app
					EFS.RC.CC.Messages.Communicator.Lync.Context.Aplication contextApplication = message as EFS.RC.CC.Messages.Communicator.Lync.Context.Aplication;

					// check
					if (contextApplication.Context != "agent")
					{
						throw new System.InvalidOperationException(System.String.Format("Agent context expected but received {0}", contextApplication.Context));
					}
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Conversation.Hold)
				{
					// hold
					customerSession.OfficeServiceConference.BeginHoldCustomerConversation();
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Conversation.Resume)
				{
					// hold
					customerSession.OfficeServiceConference.BeginResumeCustomerConversation();
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Recorder.Pause)
				{
					// hold
					customerSession.OfficeServiceConference.BeginPauseRecorder();
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Recorder.Resume)
				{
					// hold
					customerSession.OfficeServiceConference.BeginResumeRecorder();
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Context.HW)
				{
					// cast
					EFS.RC.CC.Messages.Communicator.Lync.Context.HW hwMessage = message as EFS.RC.CC.Messages.Communicator.Lync.Context.HW;

					// rtl
					EFS.RC.CC.Tools.Loggers.CHL.Add(customerSession.ConversationGuid, null, null, null, null, hwMessage.PartyID, hwMessage.ApplicantID, null, EFS.RC.CC.Events.Conversation.Discovery_HW);
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Context.Classification)
				{
					// cast
					EFS.RC.CC.Messages.Communicator.Lync.Context.Classification classMessage = message as EFS.RC.CC.Messages.Communicator.Lync.Context.Classification;

					// rtl
					EFS.RC.CC.Tools.Loggers.CHL.Add(customerSession.ConversationGuid, null, null, null, null, null, null, (System.Int32)classMessage.ID, EFS.RC.CC.Events.Conversation.Discovery_Class);
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Context.HuntGroupInvite)
				{
					// cast
					customerSession.OfficeServiceConference.SetInviteHuntGroup(message as EFS.RC.CC.Messages.Communicator.Lync.Context.HuntGroupInvite, OperatorPresence.User);
				}
				else if (message is EFS.RC.CC.Messages.Communicator.Lync.Context.Questionnaire)
				{
					// make questionare 
					EFS.RC.CC.Tools.AVR.Questionare stepByStepAVRSession = new EFS.RC.CC.Tools.AVR.Questionare(CustomerSession);

					// start
					stepByStepAVRSession.BeginStartup();

					// cast
					customerSession.OfficeServiceConference.SetStepByStepSession(stepByStepAVRSession);
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		// send participants
		private void SendCallerToConversationContext()
		{
			// get message
			EFS.RC.CC.Messages.Customer.Caller callerCustomerMessage = customerSession.GetCallerInformationMessage();

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != contextChannel)
				{
					// send info
					contextChannel.BeginDataSend(callerCustomerMessage);
				}
			}
		}

		/// <summary>
		/// Send status into conversation context
		/// </summary>
		/// <param name="message"></param>
		private void SendConversationStatusToConversationContext()
		{
			// get message
			EFS.RC.CC.Messages.Communicator.Lync.Conversation.Info conversationInfoMessage = customerSession.OfficeServiceConference.GetConversationInfoMessage();

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != contextChannel)
				{
					// send this info
					contextChannel.BeginDataSend(conversationInfoMessage);
				}
			}
		}

		/// <summary>
		/// Context message received
		/// </summary>
		/// <param name="message"></param>
		private void OnContextEstablished(EFS.RC.CC.Channels.Contexts.Operators.Operator operatorContextChannel)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnContextEstablished", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnContextEstablished", this); 

			// check
			EFS.Common.Verify.Reference(operatorContextChannel, "operatorContextChannel");

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check 
				if (!operatorContextChannel.Equals(this.contextChannel))
				{
					throw new System.InvalidOperationException("Context Channel established is different then own");
				}

				BeginAsyncProcess2(SendCallerToConversationContext);

				// start repeater
				contextChannelUpdateRepeater = new Tools.Repeater.Callback(1, SendConversationStatusToConversationContext);
				contextChannelUpdateRepeater.BeginStartup();
			}
		}

		/// <summary>
		/// Office service conference terminated
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnOfficeServiceConferenceTerminated(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnOfficeServiceConferenceTerminated", this); 

			// check mine
			EFS.Common.Verify.Condition(CustomerSession.OfficeServiceConference.Equals(officeServiceConference), "Terminated Office Service Conference is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Start tone controller
		/// </summary>
		/// <param name="participantEndpoint"></param>
		protected void InitToneController()
		{	
			// log
			EFS.EventLog.Log.LogInfo("InitToneController", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "InitToneController", this); 

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null != toneController)
				{
					throw new System.InvalidOperationException("Tone Controler already Initialized");
				}

				// make controller
				toneController = new EFS.RC.CC.Tools.Tone.Controler(this, OnToneReceived);
				toneController.BeginStartup();
			}
		}

		/// <summary>
		/// Tone received 
		/// </summary>
		/// <param name="tone"></param>
		/// <param name="volume"></param>
		private void OnToneReceived(System.Int32 tone, System.Single volume)
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnToneReceived", this);

			try
			{
				// send to customer
				customerSession.SendTone(tone, volume);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// On music terminated
		/// </summary>
		/// <param name="player"></param>
		private void OnPlayerTerminated(EFS.RC.CC.Tools.Players.Audio player)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnPlayerTerminated", this);

			// check
			EFS.Common.Verify.Reference(player, "player");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnPlayerTerminated", this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (players.Contains(player))
				{
					// remove
					players.Remove(player);
				}
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Cleanup agent session
		/// </summary>
		protected abstract void CleanupSession();

		/// <summary>
		/// Shut down
		/// </summary>
		private void Shutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (status != SessionStatus.Terminated)
				{
					// set terinated
					status = SessionStatus.Terminated;

					// Session is terminated, notify 
					AsinkTerminatedDelegate.Start();
				}
				else
				{
					// already terminated
					return;
				}
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "Shutdown", this); 

			// terminated
			EFS.RC.CC.Tools.Loggers.CHL.Add(customerSession.ConversationGuid, null, null, operatorPresence.User.ObjectGuid, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Terminate);

			// stop repeater
			BeginAsyncProcess2(StopContextChannelRepeater);

			// stop player
			BeginAsyncProcess2(StopPlayers);

			// stop context
			BeginAsyncProcess2(StopContextChannel);

			// cleanup
			BeginAsyncProcess2(CleanupSession);
		}

		/// <summary>
		/// Stop Context channel
		/// </summary>
		private void StopContextChannelRepeater()
		{
			EFS.EventLog.Log.LogInfo("StopContextChannelRepeater", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check channel availale
				if (null != contextChannelUpdateRepeater)
				{
					// terminate channel
					contextChannelUpdateRepeater.BeginShutdown();
					contextChannelUpdateRepeater = null;
				}
			}
		}

		/// <summary>
		/// Stop Context channel
		/// </summary>
		private void StopContextChannel()
		{
			EFS.EventLog.Log.LogInfo("StopContextChannel", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check channel availale
				if (null != contextChannel)
				{
					// terminate channel
					contextChannel.BeginShutdown();
					contextChannel = null;
				}
			}
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnShutdownComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
